#include <stdlib.h>
#include <stdio.h>
#include <fstream>
#include <string>
#include <windows.h>
#include <GL/glew.h>
#include <GL/glut.h>
#include <iostream>
#include <math.h>

using namespace std;

int windowWidth = 800;
int windowHeight = 600;

float _AxisTheta[3] = { 0.0, 0.0, 0.0 };
float _SelfRotateTheta[3] = { 0.0, 0.0, 0.0 };
typedef GLfloat point[3];

float ctrlPoints[4][4][3] = { { { -0.33, -0.33, 0.0 }, { -0.16, -0.16, 0.0 }, {
		0.0, -0.16, 0.0 }, { 0.33, -0.16, 0.0 } }, // P00, P01, P02, P03
		{ { -0.33, 0.0, 0.0 }, { -0.16, 0.0, 0.0 }, { 0.0, 0.0, 0.0 }, { 0.16,
				0.0, 0.0 } }, // P10, P11, P12, P13
		{ { -0.5, 0.16, 0.0 }, { -0.16, 0.16, 0.0 }, { 0.0, 0.16, 0.0 }, {
				0.33, 0.16, 0.0 } }, // P20, P21, P22, P23
		{ { -0.5, 0.33, 0.0 }, { -0.16, 0.33, 0.0 }, { 0.0, 0.5, 0.0 }, { 0.16,
				0.45, 0.0 } } // P30, P31, P32, P33
};

int _axis = 1;
float PI = 3.1415926;
int mouseLocX, mouseLocY = 0;
int myFrame, myTime, timebase = 0;
float fps = 0.0;
char s[90];
#define MAX_CHAR        128

//These parameters are for material
GLfloat ambient[] = { 0.2, 0.2, 0.2, 1.0 };
GLfloat diffuse[] = { 0.2, 0.4, 0.8, 1.0 }; //the color of the material
GLfloat specular[] = { 0.8, 0.8, 0.8, 1.0 }; //specular is white
GLfloat shine = 100.0;
GLfloat emission[] = { 0.1, 0.1, 0.1, 1.0 };
//These parameters are for light
GLfloat light0_pos[] = { 1.0, 2.0, 4, 0, 1.0 };
GLfloat diffuse0[] = { 0.3, 0.6, 0.3, 1.0 };
GLfloat ambient0[] = { 0.1, 0.1, 0.1, 1.0 };
GLfloat specular0[] = { 0.1, 0.1, 0.1, 1.0 };

// This tells us if we want lines or fill mode
bool g_bRenderMode = true;
bool g_bSmoothMode = true;
bool g_bControlPointMode = true;
bool g_bSoftwareSubdivisionMode = false;
bool g_bPhongShaderMode = false;
//this tells us if we want subdivision cycle or not
bool g_bsubdivisionMode = false;
int subdivisionMesh[5] = { 4, 7, 13, 25, 49 };
int subdivisionSoftwareMesh[5] = { 2, 3, 4, 5, 6 };
int subIndex = 0;

//These parameters are for menu creating
int mainMenu;
int subdivisionMenu;
int shadeMenu;
int rendererMenu;

//These parameters are for file output
ofstream myFile;

//Handlers for our vertex, geometry, and fragment shaders
GLuint v, f, f2, p, g;

char *textFileRead(char *fn) {
	FILE *fp;
	char *content = NULL;
	int count = 0;
	if (fn != NULL) {
		fp = fopen(fn, "rt");
		if (fp != NULL) {
			fseek(fp, 0, SEEK_END);
			count = ftell(fp);
			rewind(fp);
			if (count > 0) {
				content = (char *) malloc(sizeof(char) * (count + 1));
				count = fread(content, sizeof(char), count, fp);
				content[count] = '\0';
			}
			fclose(fp);
		}
	}
	return content;
}

int textFileWrite(char *fn, char *s) {
	FILE *fp;
	int status = 0;
	if (fn != NULL) {
		fp = fopen(fn, "w");
		if (fp != NULL) {
			if (fwrite(s, sizeof(char), strlen(s), fp) == strlen(s))
				status = 1;
			fclose(fp);
		}
	}
	return (status);
}
void normal(point n, point p, point q, point r) {
	n[0] = (q[1] - p[1]) * (r[2] - p[2]) - (q[2] - p[2]) * (r[1] - p[1]);
	n[1] = (q[2] - p[2]) * (r[0] - p[0]) - (q[0] - p[0]) * (r[2] - p[2]);
	n[2] = (q[0] - p[0]) * (r[2] - p[2]) - (q[2] - p[2]) * (r[0] - p[0]);
}

void quadrilateral(point p[4][4]) {
	//	glBegin(GL_LINE_LOOP);
	glBegin(GL_QUADS);
	point n;
	normal(n, p[0][0], p[3][0], p[3][3]);
	//	glNormal3fv(n);

	glNormal3fv(p[0][0]);
	glVertex3fv(p[0][0]);
	glNormal3fv(p[3][0]);
	glVertex3fv(p[3][0]);
	glNormal3fv(p[3][3]);
	glVertex3fv(p[3][3]);
	glNormal3fv(p[0][3]);
	glVertex3fv(p[0][3]);
	glEnd();
}

void divide_curve(point c[4], point r[4], point l[4]) {

	/* division of convex hull of Bezier curve */

	int i;
	point t;
	for (i = 0; i < 3; i++) {
		l[0][i] = c[0][i];
		r[3][i] = c[3][i];
		l[1][i] = (c[1][i] + c[0][i]) / 2;
		r[2][i] = (c[2][i] + c[3][i]) / 2;
		t[i] = (l[1][i] + r[2][i]) / 2;
		l[2][i] = (t[i] + l[1][i]) / 2;
		r[1][i] = (t[i] + r[2][i]) / 2;
		l[3][i] = r[0][i] = (l[2][i] + r[1][i]) / 2;
	}
}

void transpose(point a[4][4]) {

	/* transpose wastes time but makes program more readable */

	int i, j, k;
	GLfloat tt;
	for (i = 0; i < 4; i++)
		for (j = i; j < 4; j++)
			for (k = 0; k < 3; k++) {
				tt = a[i][j][k];
				a[i][j][k] = a[j][i][k];
				a[j][i][k] = tt;
			}
}

void divide_patch(point p[4][4], int n) {
	point q[4][4], r[4][4], s[4][4], t[4][4];
	point a[4][4], b[4][4];
	int k;
	if (n == 0)
		quadrilateral(p); /* draw patch if recursion done */

	/* subdivide curves in u direction, transpose results, divide
	 in u direction again (equivalent to subdivision in v) */

	else {
		for (k = 0; k < 4; k++)
			divide_curve(p[k], a[k], b[k]);
		transpose(a);
		transpose(b);
		for (k = 0; k < 4; k++) {
			divide_curve(a[k], q[k], r[k]);
			divide_curve(b[k], s[k], t[k]);
		}

		/* recursive division of 4 resulting patches */

		divide_patch(q, n - 1);
		divide_patch(r, n - 1);
		divide_patch(s, n - 1);
		divide_patch(t, n - 1);
	}
}
void drawMesh() {
	int i, j = 0;
	if (g_bSoftwareSubdivisionMode)
		divide_patch(ctrlPoints, subdivisionSoftwareMesh[subIndex]);
	else {

		glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 3 * 4, 4,
				&ctrlPoints[0][0][0]);
		// simple way to draw mesh.
		//		glMapGrid2f(subdivisionMesh[subIndex], 0.0, 1.0, subdivisionMesh[subIndex],
		//				0.0, 1.0);
		//		glEvalMesh2(GL_FILL, 0, subdivisionMesh[subIndex], 0,
		//				subdivisionMesh[subIndex]);

		// a little hard way to draw mesh.

		for (i = 0; i < subdivisionMesh[subIndex]; i++) {

			glBegin(GL_QUAD_STRIP);

			for (j = 0; j <= subdivisionMesh[subIndex]; j++) {

				glEvalCoord2f((GLfloat) i / subdivisionMesh[subIndex],
						(GLfloat) j / subdivisionMesh[subIndex]);
				glEvalCoord2f((GLfloat) (i + 1) / subdivisionMesh[subIndex],
						(GLfloat) j / subdivisionMesh[subIndex]);

			}

			glEnd();

		}
	}
	//draw control point.
	if (g_bControlPointMode) {
		glPointSize(4.0);
		glBegin(GL_POINTS);
		for (j = 0; j < 4; j++) {
			for (i = 0; i < 4; i++) {
				glVertex3fv(&ctrlPoints[i][j][0]);
			}
		}
		glEnd();
	}
}

void init() {
	mainMenu = 1;
	subdivisionMenu = 2;
	shadeMenu = 3;

	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glMaterialfv(GL_FRONT, GL_AMBIENT, ambient);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse);
	glMaterialfv(GL_FRONT, GL_SPECULAR, specular);
	glMaterialf(GL_FRONT, GL_SHININESS, shine);

	glMaterialfv(GL_FRONT, GL_EMISSION, emission);

	glEnable(GL_NORMALIZE);

	glLightfv(GL_LIGHT0, GL_POSITION, light0_pos);
	glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
	glLightfv(GL_LIGHT0, GL_SPECULAR, specular);

	//			glEnable(GL_CULL_FACE);
	//			glCullFace(GL_BACK);
	glEnable(GL_MAP2_VERTEX_3);
	glEnable(GL_AUTO_NORMAL);
	myFile.open("fps.txt");

}

//use windows library to draw string
void drawString(const char * str) {
	static int isFirstCall = 1;
	static GLuint lists;

	if (isFirstCall) {

		isFirstCall = 0;

		lists = glGenLists(MAX_CHAR);

		wglUseFontBitmaps(wglGetCurrentDC(), 0, MAX_CHAR, lists);
	}

	for (; *str != '\0'; ++str)
		glCallList(lists + *str);
}
void display() {
	glClearColor(0.0, 0.0, 0.0, 1.0);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	// Change the rendering mode to and from lines or triangles
	if (g_bRenderMode) {
		// Render the triangles in fill mode
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	} else {
		// Render the triangles in wire frame mode
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	}
	if (g_bSmoothMode) {
		// Render the triangles in smooth mode
		glShadeModel(GL_SMOOTH);
	} else {
		// Render the triangles in flat  mode
		glShadeModel(GL_FLAT);
	}

	glTranslatef(0.0, 0.0, -6.0);
	glRotatef(_AxisTheta[0], 1.0, 0.0, 0.0);
	glRotatef(_AxisTheta[1], 0.0, 0.1, 0.0);

	glScalef(6, 6, 6);

	//draw the mesh here
	drawMesh();

	//help information
	//		glRasterPos2f(-6.0f, 5.0f);
	//		drawString("'S' to toggle the subdivision mode(cycles in 2 seconds)");
	//		glRasterPos2f(-6.0f, 4.7f);
	//		drawString("'F' to toggle the wire frame mode");
	//		glRasterPos2f(-6.0f, 4.4f);
	//		drawString("'B' to toggle the smooth render mode");
	//		glRasterPos2f(-6.0f, 4.1f);
	//		drawString("'L' to toggle the shader mode");
	//		glRasterPos2f(-6.0f, 3.8f);
	//		drawString("'C' to Display control points");
	//		glRasterPos2f(-6.0f, 3.5f);
	//		drawString("'Q' to quit the program");

	myFrame++;// used to calculate and display frame rate on window title.
	myTime = glutGet(GLUT_ELAPSED_TIME);
	if (myTime - timebase > 2000) {
		fps = myFrame * 1000.0 / (myTime - timebase);
		sprintf(s, "Assignment 3 by Zhiyi Wu FPS:%4.2f", fps);

		myFile << fps << endl;

		//these following code is used to cycle the subdivision mode
		if (g_bsubdivisionMode)
			subIndex += 1;
		if (subIndex > 4)
			subIndex -= 5;
		glutSetWindowTitle(s);
		timebase = myTime;
		myFrame = 0;
	}

	glutSwapBuffers();

}
//this source of this setShaders code is come from Lighthouse3d.com
void setShaders() {
	//a few strings
	// will hold onto the file read in!
	char *vs = NULL, *fs = NULL, *gs = NULL;

	//char *fs2 = NULL;
	//First, create our shaders
	v = glCreateShader(GL_VERTEX_SHADER);
	f = glCreateShader(GL_FRAGMENT_SHADER);
	//	g = glCreateShader(GL_GEOMETRY_SHADER_EXT);

	//Read in the programs
	char VertexShaderFile[] = "vertex1.txt";
	char FragmentShaderFile[] = "fragment1.txt";

	vs = textFileRead(VertexShaderFile);
	fs = textFileRead(FragmentShaderFile);

	//Setup a few constant pointers for below
	const char * ff = fs;
	const char * vv = vs;
	//const char * gg = gs;
	glShaderSource(v, 1, &vv, NULL);
	glShaderSource(f, 1, &ff, NULL);
	//glShaderSource(g, 1, &gg, NULL);

	free(vs);
	free(fs);
	free(gs);

	glCompileShader(v);
	glCompileShader(f);
	//	glCompileShader(f2);
	//	glCompileShader(g);

	p = glCreateProgram();

	glAttachShader(p, f);
	glAttachShader(p, v);
	//glAttachShader(p,f2);
	//glAttachShader(p,g);
	glProgramParameteriEXT(p, GL_GEOMETRY_INPUT_TYPE_EXT, GL_LINES);
	glProgramParameteriEXT(p, GL_GEOMETRY_OUTPUT_TYPE_EXT, GL_LINE_STRIP);

	int temp;
	glGetIntegerv(GL_MAX_GEOMETRY_OUTPUT_VERTICES_EXT, &temp);
	glProgramParameteriEXT(p, GL_GEOMETRY_VERTICES_OUT_EXT, temp);

	glLinkProgram(p);
	glUseProgram(p);

}

void spinCube() {
	//	_AxisTheta[_axis] += 0.3;
	//	_SelfRotateTheta[_axis] += 1.1;
	int i, j = 0;
	int tempTheta = 0;

	//control the mesh moving in a interesting way...
	for (j = 0; j < 4; j++) {

		for (i = 0; i < 4; i++)

			ctrlPoints[j][i][2] = sin((myTime / 7 + tempTheta) * PI / 180) / 6;

		tempTheta += 90;
	}

	if (_AxisTheta[_axis] > 360.0)
		_AxisTheta[_axis] -= 360.0;
	if (_SelfRotateTheta[_axis] > 360.0)
		_SelfRotateTheta[_axis] -= 360.0;

	glutPostRedisplay();
}

void mouse(int button, int state, int x, int y) {
	if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
		mouseLocX = x;
		mouseLocY = y;
	}

}

void mykey(unsigned char key, int x, int y) //used to clear the canvas
{
	if ((key == 'C') | (key == 'c')) {
		// Display control points to manipulate
		g_bControlPointMode = !g_bControlPointMode;
	}
	if ((key == 'F') | (key == 'f')) {
		// We want to be able to turn wire frame on and off, so let's switch it
		g_bRenderMode = !g_bRenderMode;
	}
	if ((key == 'B') | (key == 'b')) {
		// We want to be able to turn wire frame on and off, so let's switch it
		g_bSmoothMode = !g_bSmoothMode;
	}
	if ((key == 'S') | (key == 's')) {
		// toggle to cycle the subdivision
		g_bsubdivisionMode = !g_bsubdivisionMode;
	}
	if ((key == 'R') | (key == 'r')) {
		// toggle to cycle the hardware or software surface rendering
		g_bSoftwareSubdivisionMode = !g_bSoftwareSubdivisionMode;
	}
	if ((key == 'L') | (key == 'l')) {
		// toggle to Shader lighting

		if (g_bPhongShaderMode) {
			glUseProgram(p);
			g_bPhongShaderMode = !g_bPhongShaderMode;
		} else {
			glUseProgram(0);
			g_bPhongShaderMode = !g_bPhongShaderMode;
		}
	}
	if ((key == 'Q') | (key == 'q')) {
		// quit and export the fps data to txt file
		myFile.close();
		exit(0);
	}

}

void motion(int x, int y) { //control the draw function when mouse button down and move


	if (mouseLocX != x) {
		_AxisTheta[1] += (x - mouseLocX);

	}
	if (mouseLocY != y) {
		_AxisTheta[0] += (y - mouseLocY);

	}
	mouseLocX = x;
	mouseLocY = y;

}

void AlternativePerspective(GLfloat fovy, GLfloat aspect, GLfloat zNear,
		GLfloat zFar) {
	GLfloat xmin, xmax, ymin, ymax;

	ymax = zNear * tan(fovy * PI / 360.0);
	ymin = -ymax;
	xmin = ymin * aspect;
	xmax = ymax * aspect;

	glFrustum(xmin, xmax, ymin, ymax, zNear, zFar);
}

void menu(int id) {

	switch (id) {
	case 3:
		glutPostRedisplay();
		break;
	case 4:
		myFile.close();
		exit(0);
		break;
	}
}

void subdivisionmenu(int id) {

	switch (id) {
	case 0:
		subIndex = 0;
		break;
	case 1:
		subIndex = 1;
		break;
	case 2:
		subIndex = 2;

		break;
	case 3:
		subIndex = 3;
		break;
	case 4:
		subIndex = 4;
		break;

	}
}

void shademenu(int id) {
	switch (id) {
	case 0:
		glUseProgram(0);
		break;
	case 1:
		glUseProgram(p);
		break;

	}

}

void renderermenu(int id) {
	switch (id) {
	case 0:
		g_bSoftwareSubdivisionMode = false;
		break;
	case 1:
		g_bSoftwareSubdivisionMode = true;
		break;

	}

}
void myReshape(int w, int h) {
	glViewport(0, 0, w, h);
	windowWidth = w;
	windowHeight = h;
	glMatrixMode(GL_PROJECTION);//Applies subsequent matrix operations to the modelview matrix stack. this is used to contril the
	glLoadIdentity();//glLoadIdentity replaces the current matrix with the identity matrix.
	glOrtho(-2.0, 2.0, -2.0, 2.0, 2.0, -2.0);
	AlternativePerspective(50.0, (float) w / (float) h, 2.0, 10.0);

	glMatrixMode(GL_MODELVIEW);//Applies subsequent matrix operations to the projection matrix stack.

}

int main(int argc, char** argv) {
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(800, 600);
	glutInitWindowPosition((glutGet(GLUT_SCREEN_WIDTH) - windowWidth) / 2,
			(glutGet(GLUT_SCREEN_HEIGHT) - windowHeight) / 2); // Set the windows centered in screen based on native resolution.

	glutCreateWindow("Assignment 3 by Zhiyi Wu");
	init();
	subdivisionMenu = glutCreateMenu(subdivisionmenu); //create subdivision menu
	glutAddMenuEntry("subdivision 0 4x4", 0);
	glutAddMenuEntry("subdivision 1 7x7", 1);
	glutAddMenuEntry("subdivision 2 13x13", 2);
	glutAddMenuEntry("subdivision 3 25x25", 3);
	glutAddMenuEntry("subdivision 4 49x49", 4);
	glutAttachMenu(GLUT_RIGHT_BUTTON);

	shadeMenu = glutCreateMenu(shademenu); //create shade menu
	glutAddMenuEntry("Vertex Shade", 0);
	glutAddMenuEntry("Fragment Shade", 1);

	glutAttachMenu(GLUT_RIGHT_BUTTON);

	rendererMenu = glutCreateMenu(renderermenu); //create shade menu
	glutAddMenuEntry("Hardware Subdivision", 0);
	glutAddMenuEntry("Software Subdivision", 1);

	glutAttachMenu(GLUT_RIGHT_BUTTON);
	mainMenu = glutCreateMenu(menu);// create main menu
	glutAddSubMenu("Subdivision", subdivisionMenu);
	glutAddSubMenu("Shade Mode", shadeMenu);
	glutAddSubMenu("Renderer Mode", rendererMenu);
	glutAddMenuEntry("Clear Screen", 3);
	glutAddMenuEntry("Exit", 4);
	glutAttachMenu(GLUT_RIGHT_BUTTON);

	glutDisplayFunc(display);
	glutIdleFunc(spinCube);
	glutReshapeFunc(myReshape);
	glutMouseFunc(mouse);
	glutMotionFunc(motion);
	glutKeyboardFunc(mykey);

	glEnable(GL_DEPTH_TEST);
	glewInit();
	if (glewIsSupported("GL_VERSION_2_1"))
		printf("Ready for OpenGL 2.1\n");
	else {
		printf("OpenGL 2.1 not supported\n");
		exit(1);
	}
	if (GLEW_ARB_vertex_shader && GLEW_ARB_fragment_shader
			&& GL_EXT_geometry_shader4)
		printf("Ready for GLSL - vertex, fragment, and geometry units\n");
	else {
		printf("Not totally ready :( \n");
		exit(1);
	}
	setShaders();
	glutMainLoop();
	return 0;
}
